package generic.swing;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import javax.swing.table.AbstractTableModel;

/**
 * Converts a list into an AbstractTable.
 *
 * Any method starting with get that takes no paramaters will become a column.
 *
 * Caution: AbstractTable relys heavily on random access to lists, LinkedLists
 * are probably a bad idea.
 *
 * @author Adam Scarr <scarr.adam@gmail.com>
 */
public class List2AbstractTableAdapater<Type> extends AbstractTableModel {
    private List<Type> list;
    private List<Method> methods = new ArrayList<Method>();
    private String[] methodNames;
    private String[] exclusions;
    private List<String> headings = new ArrayList<String>();
    private boolean introspectionRequired = false;

    public List2AbstractTableAdapater() {
        setList(new ArrayList<Type>());
    }

    public List2AbstractTableAdapater(List<Type> list) {
        setList(list);
    }

    public List2AbstractTableAdapater(List<Type> list, String[] exclusions) {
        this.exclusions = exclusions;
        setList(list);
    }

    public void setColumns(String[] methodNames, String[] headings) {
        this.methodNames = methodNames;
        this.headings.clear();

        for (String heading: headings) {
            this.headings.add(heading);
        }
    }

    public void add(Type ticket) {
        list.add(ticket);
        int index = list.indexOf(ticket);

        if (introspectionRequired) {
            introspect();
            fireTableStructureChanged();
        }

        fireTableRowsInserted(index, index);
    }

    private boolean methodExcluded(String string) {
        if (exclusions != null) {
            for (String exclusion: exclusions) {
                if (string.equals(exclusion)) {
                    return true;
                }
            }
        }

        // One final one that always gets excluded.
        return string.equals("getClass");
    }

    public void introspect() {
        if (list == null || list.isEmpty()) {
            introspectionRequired = true;
            return;
        }

        if (methodNames != null) {
            namedIntrospection();
        } else {
            genericIntrospection();
        }

        introspectionRequired = false;
    }

    public void genericIntrospection() {
        for (Method method: list.get(0).getClass().getDeclaredMethods()) {
            if (method.getName().startsWith("get") &&
                    method.getParameterTypes().length == 0 &&
                    !methodExcluded(method.getName())) {
                methods.add(method);
                headings.add(deCap(method.getName().substring(3)));
            }
        }
    }

    public void namedIntrospection() {
        for (String methodName: methodNames) {
            for (Method method: list.get(0).getClass().getDeclaredMethods()) {
                if (method.getName().equals(methodName)) {
                    methods.add(method);
                }
            }
        }
    }

    public void setList(List<Type> list) {
        this.list = list;
        introspect();
    }

    public Type getRow(int row) {
        return list.get(row);
    }

    public int getRowCount() {
        return list.size();
    }

    public int getColumnCount() {
        return methods.size();
    }

    public String getColumnName(int column) {
        return headings.get(column);
    }

    public void replaceHeading(String old, String name) {
        int index = headings.indexOf(old);
        if (headings.remove(old)) {
            headings.add(index, name);
        }
    }

    public boolean isCellEditable(int row, int col) {
        return false;
    }

    /**
     * Converts camelCaps to spaces.
     * @return
     */
    public String deCap(String camelCaps) {
        StringBuilder result = new StringBuilder();
        boolean lastWasLower = true;
        int pos = 1;

        // First character should always be upper case.
        result.append(Character.toString(camelCaps.charAt(0)).toUpperCase());

        while (pos < camelCaps.length()) {
            char thisChar = camelCaps.charAt(pos);

            if (lastWasLower && Character.isUpperCase(thisChar)) {
                result.append(" ");

            }

            result.append(Character.toString(thisChar));
            lastWasLower = !Character.isUpperCase(thisChar);
            pos++;
        }

        return result.toString();
    }

    public Object getValueAt(int rowIndex, int columnIndex) {
        try {
            return methods.get(columnIndex).invoke(list.get(rowIndex), new Object[0]);
        } catch (IllegalAccessException ex) {
            ex.printStackTrace();
        } catch (IllegalArgumentException ex) {
            ex.printStackTrace();
        } catch (InvocationTargetException ex) {
            ex.printStackTrace();
        }
        return null;
    }

}
